package cn.quang.utils.lang;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 日期工具类
 *
 * @author: 钱国红
 * @since: 2018/04/23 17:39
 */
@Slf4j
public final class DateUtils {
    private static final Object lockObj = new Object();
    /**
     * 存放不同的日期模板格式的sdf的Map
     */
    private static final Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<>();

    public static final String PATTERN_DAY = "yyyy-MM-dd";
    public static final String PATTERN_DAY_NO_UNDERLINE = "yyyyMMdd";
    public static final String PATTERN_MONTH_NO_UNDERLINE = "yyyyMM";
    public static final String PATTERN_DAY_FULL_NO_UNDERLINE = "yyyyMMddHHmmss";
    public static final String PATTERN_FULL = "yyyy-MM-dd HH:mm:ss";
    public static final String PATTERN_END_OF_MINUTE = "yyyy-MM-dd HH:mm";
    public static final String PATTERN_FULL_WITH_MILLSECOND = "yyyy-MM-dd HH:mm:ss,SSS";
    public static final String PATTERN_FULL_WITH_MILLSECOND2 = "yyyyMMddHHmmssSSS";
    public static final String DEFAULT_DATA_STR = "1900-01-01T00:00:00";
    public static final String DEFAULT_DATA_STR_WITH_MILLISECOND = "1900-01-01T00:00:00.000";
    public static final String DEFAULT_DATA_STR2 = "1900-01-01 00:00:00";
    public static final String DEFAULT_DATA_STR3 = "1900-01-01";
    public static final String PATTERN_FULL2 = "yyyy-MM-dd'T'HH:mm:ss";
    public static final String PATTERN_FULL2_WITH_MILLISECOND = "yyyy-MM-dd'T'HH:mm:ss.SSS";
    public static final String PATTERN_HOUR_SECOND = "HH:mm";
    public static final String PATTERN_FULL3 = "yyyy/MM/dd HH:mm:ss";
    public static final String PATTERN_FUll_WITH_7_CLOCK = "yyyy-MM-dd 07:00:00";
    public static final String PATTERN_HOUR = "HH:mm";

    public static final long TWENTY_FOUR_HOURS = 24 * 60 * 60;

    private static final String[] DAY_OF_WEEK_MAPPING = new String[]{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};

    /**
     * 默认的日期
     */
    public static Date DEFAULT_DATE = parse(DEFAULT_DATA_STR2, PATTERN_FULL);

    private DateUtils() {
    }

    public static String format(Date date, String pattern) {
        if (date == null) {
            return null;
        }
        return getSimpleDateFormat(pattern).format(date);
    }

    public static String formatTimeZone(Date date, String pattern, TimeZone timeZone) {
        if (date == null) {
            return null;
        }
        return getSimpleDateFormatTimeZone(pattern, timeZone).format(date);
    }

    public static String formatTimeZoneBj(Date date, String pattern) {
        if (date == null) {
            return null;
        }
        return formatTimeZone(date, pattern, TimeZone.getTimeZone("GMT+8"));
    }

    /**
     * 用 yyyy-MM-dd HH:mm:ss 格式化时间
     *
     * @param date
     * @return
     */
    public static String format(Date date) {
        return format(date, PATTERN_FULL);
    }

    public static Date parse(String dateStr, String pattern) {
        if (dateStr == null) {
            return null;
        }
        try {
            return getSimpleDateFormat(pattern).parse(dateStr);
        } catch (ParseException e) {
            log.error(String.format("parse('%s', '%s')出现异常！", dateStr, pattern), e);
            return null;
        }
    }

    public static Date parseDate(String dateStr) {
        try {
            return getSimpleDateFormat(PATTERN_DAY).parse(dateStr);
        } catch (ParseException e) {
            log.error(String.format("parse('%s', '%s')出现异常！", dateStr, PATTERN_DAY), e);
            return null;
        }
    }

    /**
     * 给指定日期添加指定天数
     *
     * @param date
     * @param days
     * @return
     */
    public static Date addDays(Date date, int days) {
        Calendar calendar = Calendar.getInstance();
        if (date != null) {
            calendar.setTime(date);
        }

        calendar.add(Calendar.DAY_OF_MONTH, days);
        return calendar.getTime();
    }

    /**
     * 给指定日期添加指定分钟
     *
     * @param date
     * @param minutes
     * @return
     */
    public static Date addMinutes(Date date, int minutes) {
        Calendar calendar = Calendar.getInstance();
        if (date != null) {
            calendar.setTime(date);
        }

        calendar.add(Calendar.MINUTE, minutes);
        return calendar.getTime();
    }

    public static int getDayOfWeek(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        return cal.get(Calendar.DAY_OF_WEEK) - 1;
    }

    /**
     * 根据日期查询星期的描述，例如"星期日"
     * @param date
     * @return
     */
    public static String getDayOfWeekDescription(Date date) {
        int dayOffWeek = getDayOfWeek(date);
        if(dayOffWeek >=0 && dayOffWeek < DAY_OF_WEEK_MAPPING.length) {
            return DAY_OF_WEEK_MAPPING[dayOffWeek];
        }

        return "";
    }

    /**
     * 返回当前时间 yyyy-MM-dd HH:mm:ss
     *
     * @return
     */
    public static String getCurrentTimeFormat() {
        Calendar calender = Calendar.getInstance();
        return format(calender.getTime(), PATTERN_FULL);
    }

    /**
     * 返回当前时间 yyyy-MM-dd HH:mm:ss,SSS
     *
     * @return
     */
    public static String getCurrentTimeWithMillFormat() {
        Calendar calender = Calendar.getInstance();
        return format(calender.getTime(), PATTERN_FULL_WITH_MILLSECOND);
    }

    /**
     * 将起止时间转换为耗时描述
     *
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @return 可读描述
     */
    public static String getManualSpendTimeDescription(long startTime, long endTime) {
        return getManualSpendTimeDescription(endTime - startTime);
    }

    /**
     * 将耗时毫秒值转换为耗时描述
     *
     * @param spendTime 耗时毫秒值
     * @return 可读描述
     */
    public static String getManualSpendTimeDescription(long spendTime) {
        Calendar c = Calendar.getInstance();
        c.setTimeInMillis(spendTime);
        StringBuilder sb = new StringBuilder();

        if (c.get(Calendar.HOUR) > 8) {
            sb.append(c.get(Calendar.HOUR) - 8).append("小时");
        }

        if (c.get(Calendar.MINUTE) > 0) {
            sb.append(c.get(Calendar.MINUTE)).append("分");
        }

        if (c.get(Calendar.SECOND) > 0) {
            sb.append(c.get(Calendar.SECOND)).append("秒");
        }

        if (c.get(Calendar.MILLISECOND) > 0) {
            sb.append(c.get(Calendar.MILLISECOND)).append("毫秒");
        }

        if (sb.length() > 0) {
            return sb.toString();
        }

        return "0";
    }

    /**
     * 获取指定日期的零点
     *
     * @param date 日期
     * @return 指定日期的当日0点
     */
    public static Date getZeroClockOfDay(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);

        return calendar.getTime();
    }

    /**
     * 获取指定日期的23:59:59.999
     *
     * @param date 日期
     * @return 指定日期的当日23:59:59.999
     */
    public static Date getTailClockOfDay(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        calendar.set(Calendar.MILLISECOND, 999);

        return calendar.getTime();
    }

    /**
     * 查询是否是周末
     *
     * @param date 指定日期
     * @return 是否周末
     */
    public static boolean isWeekend(Date date) {
        if (date == null) {
            return false;
        }

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int weekOfDay = calendar.get(Calendar.DAY_OF_WEEK);
        return weekOfDay == Calendar.SUNDAY || weekOfDay == Calendar.SATURDAY;
    }

    /**
     * 是否周六
     *
     * @param date 指定日期
     * @return 是否周六
     */
    public static boolean isSaturday(Date date) {
        if (date == null) {
            return false;
        }

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int weekOfDay = calendar.get(Calendar.DAY_OF_WEEK);
        return weekOfDay == Calendar.SATURDAY;
    }

    /**
     * 是否周日
     *
     * @param date 指定日期
     * @return 是否周日
     */
    public static boolean isSunday(Date date) {
        if (date == null) {
            return false;
        }

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int weekOfDay = calendar.get(Calendar.DAY_OF_WEEK);
        return weekOfDay == Calendar.SUNDAY;
    }

    /**
     * 获取指定时间的月初，例如2019-05-01 00:00:00
     *
     * @param date 指定日期
     * @return 指定日期所属月份的月初
     */
    public static Date getFirstDayOfMonth(Date date) {
        Date zeroClockDate = getZeroClockOfDay(date == null ? new Date() : date);

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(zeroClockDate);
        calendar.set(Calendar.DATE, 1);

        return calendar.getTime();
    }

    /**
     * 获取本月月初，例如2019-05-01 00:00:00
     *
     * @return 本月月初
     */
    public static Date getFirstDayOfMonth() {
        return getFirstDayOfMonth(null);
    }

    /**
     * 获取指定时间的月末，例如2019-05-31 23:59:59.999
     *
     * @param date 指定日期
     * @return 指定日期所属月份的月末
     */
    public static Date getLastDayOfMonth(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date == null ? new Date() : date);
        calendar.add(Calendar.MONTH, 1);
        calendar.set(Calendar.DAY_OF_MONTH, 0);

        return getTailClockOfDay(calendar.getTime());
    }

    /**
     * 获取本月的月末，例如2019-05-31 23:59:59.999
     *
     * @return 指定日期所属月份的月末
     */
    public static Date getLastDayOfMonth() {
        return getLastDayOfMonth(null);
    }

    /**
     * 返回一个ThreadLocal的sdf,每个线程只会new一次sdf
     * 设置时区东八区
     *
     * @param pattern 格式化pattern
     * @return SDF
     */
    private static SimpleDateFormat getSimpleDateFormat(final String pattern) {
        ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattern);

        if (tl == null) {
            synchronized (lockObj) {
                tl = sdfMap.get(pattern);
                if (tl == null) {
                    tl = ThreadLocal.withInitial(() -> {
                        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
                        sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));
                        return sdf;
                    });
                    sdfMap.put(pattern, tl);
                }
            }
        }

        return tl.get();
    }

    /**
     * 返回一个ThreadLocal的sdf,每个线程只会new一次sdf
     * 设置时区东八区
     *
     * @param pattern 格式化pattern
     * @param timeZone 时区
     * @return SDF
     */
    private static SimpleDateFormat getSimpleDateFormatTimeZone(final String pattern, TimeZone timeZone) {
        ThreadLocal<SimpleDateFormat> tl = sdfMap.get("timeZone" + pattern);

        if (tl == null) {
            synchronized (lockObj) {
                tl = sdfMap.get(pattern);
                if (tl == null) {
                    tl = ThreadLocal.withInitial(() -> {
                        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
                        sdf.setTimeZone(timeZone);
                        return sdf;
                    });
                    sdfMap.put(pattern, tl);
                }
            }
        }

        return tl.get();
    }

    /**
     * 计算2个日期的差值（根据不同的时间单位计算出不同时间单位的差值）
     *
     * @param startTime 起始时间
     * @param endTime 终止时间
     * @param unit  时间单位
     * @return 时间差值（单位与unit参数单位相同.当end>start返回正值，否则返回负值）
     */
    public static long between(Date startTime, Date endTime, TimeUnit unit) {
        if (startTime == null || endTime == null || unit == null) {
            return 0;
        }

        long duration = endTime.getTime() - startTime.getTime();
        return unit.convert(duration, TimeUnit.MILLISECONDS);
    }

    /**
     * 获得上个月的第一天
     */
    public static Date getFirstDayOfPreMonth() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.MONTH, -1);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        return calendar.getTime();
    }

    /**
     * 获得上个月的最后一天
     */
    public static Date getLastDayOfPreMonth() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.MONTH, -1);
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        return calendar.getTime();
    }

    /**
     * 格式化时间（yyyy-MM-dd HH:mm:ss）
     * 如果时间为默认时间（1900-01-01 00:00:00），则返回""
     *
     * @param date 时间
     * @return 格式化时间字符串
     */
    public static String formatDate(Date date) {
        if (date != null && !format(date, PATTERN_FULL).equals(DEFAULT_DATA_STR2)) {
            return format(date, PATTERN_FULL);
        } else {
            return "";
        }
    }

    /**
     * 格式化时间（yyyy-MM-dd）
     * 如果时间为默认时间（1900-01-01），则返回""
     *
     * @param date 时间
     * @return 格式化时间字符串
     */
    public static String formatDate(Date date, String pattern) {
        if (date != null && !format(date, pattern).equals(DEFAULT_DATA_STR3)) {
            return format(date, pattern);
        } else {
            return "";
        }
    }

    /**
     * 取最大的日期
     *
     * @param dt1 时间1
     * @param dt2 时间2
     * @return 日期靠后的时间
     */
    public static Date max(Date dt1, Date dt2) {
        if (dt1 == null) {
            return dt2;
        }

        if (dt2 == null) {
            return dt1;
        }

        return dt1.after(dt2) ? dt1 : dt2;
    }

    /**
     * 取最小的日期
     *
     * @param dt1 时间1
     * @param dt2 时间2
     * @return 日期靠前的时间
     */
    public static Date min(Date dt1, Date dt2) {
        if (dt1 == null) {
            return dt1;
        }

        if (dt2 == null) {
            return dt2;
        }

        return dt1.before(dt2) ? dt1 : dt2;
    }

    /**
     * 输入的两个日期是否在同一个月
     * @param d1 时间1
     * @param d2 时间2
     *
     * return true:在同一个月
     */
    public static boolean isSameMonth(Date d1, Date d2){

        if(d1 == null) {
            return d2 == null;
        }

        if(d2 == null) {
            return false;
        }

        Calendar calendarD1 = Calendar.getInstance();
        calendarD1.setTime(d1);

        Calendar calendarD2 = Calendar.getInstance();
        calendarD2.setTime(d2);

        return calendarD1.get(Calendar.YEAR) == calendarD2.get(Calendar.YEAR)
                && calendarD1.get(Calendar.MONTH) == calendarD2.get(Calendar.MONTH);
    }

    /**
     * 输入的两个日期是否在同一天
     *
     * @param dt1 时间1
     * @param dt2 时间2
     * @return true:同一天
     */
    public static boolean isSameDay(Date dt1, Date dt2) {
        SimpleDateFormat fmt = new SimpleDateFormat(PATTERN_DAY_NO_UNDERLINE);
        return fmt.format(dt1).equals(fmt.format(dt2));
    }

    /**
     * 将时间单元转化为中文描述
     * @param n N天/N小时...
     * @param timeUnit 时间单位
     * @return 时间单位组装描述
     */
    public static String getTimeUnitDescription(int n, TimeUnit timeUnit) {
        String strUnit;
        if(timeUnit == TimeUnit.DAYS) {
            strUnit = "天";
        } else if(timeUnit == TimeUnit.HOURS) {
            strUnit = "小时";
        } else if(timeUnit == TimeUnit.MINUTES) {
            strUnit = "分钟";
        } else if(timeUnit == TimeUnit.SECONDS) {
            strUnit = "秒";
        } else if(timeUnit == TimeUnit.MILLISECONDS) {
            strUnit = "毫秒";
        } else if(timeUnit == TimeUnit.MICROSECONDS) {
            strUnit = "微秒";
        } else if(timeUnit == TimeUnit.NANOSECONDS) {
            strUnit = "纳秒";
        } else  {
            strUnit = "N/A";
        }

        return String.format("%d%s", n, strUnit);
    }
}
